Libraries and Functions
Libraries
library(tidyverse)
library(ggplot2)
library(Matrix)
library(Rmisc)
library(ggforce)
library(rjson)
library(cowplot)
library(RColorBrewer)
library(grid)
library(readbitmap)
library(Seurat)
Functions
The geom_spatial function is defined to make plotting your tissue image in ggplot a simple task.
geom_spatial <- function(mapping = NULL,
data = NULL,
stat = "identity",
position = "identity",
na.rm = FALSE,
show.legend = NA,
inherit.aes = FALSE,
...) {
GeomCustom <- ggproto(
"GeomCustom",
Geom,
setup_data = function(self, data, params) {
data <- ggproto_parent(Geom, self)$setup_data(data, params)
data
},
draw_group = function(data, panel_scales, coord) {
vp <- grid::viewport(x=data$x, y=data$y)
g <- grid::editGrob(data$grob[[1]], vp=vp)
ggplot2:::ggname("geom_spatial", g)
},
required_aes = c("grob","x","y")
)
layer(
geom = GeomCustom,
mapping = mapping,
data = data,
stat = stat,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
Reading in your data
Define your samples
sample_names <- c("Sample1", "Sample2")
sample_names
Define your paths
Paths should be in the same order as the corresponding sample names
image_paths <- c("/path/to/Sample1-spatial/tissue_lowres_image.png",
"/path/to/Sample2-spatial/tissue_lowres_image.png")
scalefactor_paths <- c("/path/to/Sample1-spatial/scalefactors_json.json",
"/path/to/Sample2-spatial/scalefactors_json.json")
tissue_paths <- c("/path/to/Sample1-spatial/tissue_positions_list.txt",
"/path/to/Sample2-spatial/tissue_positions_list.txt")
cluster_paths <- c("/path/to/Sample1/outs/analysis_csv/clustering/graphclust/clusters.csv",
"/path/to/Sample2/outs/analysis_csv/clustering/graphclust/clusters.csv")
matrix_paths <- c("/path/to/Sample1/outs/filtered_feature_bc_matrix.h5",
"/path/to/Sample2/outs/filtered_feature_bc_matrix.h5")
Read in down sampled images
We also need to determine the image height and width for proper plotting in the end
images_cl <- list()
for (i in 1:length(sample_names)) {
images_cl[[i]] <- read.bitmap(image_paths[i])
}
height <- list()
for (i in 1:length(sample_names)) {
height[[i]] <- data.frame(height = nrow(images_cl[[i]]))
}
height <- bind_rows(height)
width <- list()
for (i in 1:length(sample_names)) {
width[[i]] <- data.frame(width = ncol(images_cl[[i]]))
}
width <- bind_rows(width)
Convert the images to grobs
This step provides compatibility with ggplot2
grobs <- list()
for (i in 1:length(sample_names)) {
grobs[[i]] <- rasterGrob(images_cl[[i]], width=unit(1,"npc"), height=unit(1,"npc"))
}
images_tibble <- tibble(sample=factor(sample_names), grob=grobs)
images_tibble$height <- height$height
images_tibble$width <- width$width
images_tibble
Read in Clusters
clusters <- list()
for (i in 1:length(sample_names)) {
clusters[[i]] <- read.csv(cluster_paths[i])
}
head(clusters[[1]])
Combine clusters and tissue info for easy plotting
At this point we also need to adjust the spot positions by the scale factor for the image that we are using. In this case we are using the lowres image which has been resized by Space Ranger to be 600 pixels (largest dimension) but also keeps the proper proportions.
For example, if your image is 12000x11000 the image will be resized to be 600x550. If your image is 11000x12000 the image will be resized to be 550x600.
bcs <- list()
for (i in 1:length(sample_names)) {
bcs[[i]] <- read.csv(tissue_paths[i],col.names=c("barcode","tissue","row","col","imagerow","imagecol"), header = FALSE)
bcs[[i]]$imagerow <- bcs[[i]]$imagerow * scales[[i]]$tissue_lowres_scalef # scale tissue coordinates for lowres image
bcs[[i]]$imagecol <- bcs[[i]]$imagecol * scales[[i]]$tissue_lowres_scalef
bcs[[i]]$tissue <- as.factor(bcs[[i]]$tissue)
bcs[[i]] <- merge(bcs[[i]], clusters[[i]], by.x = "barcode", by.y = "Barcode", all = TRUE)
bcs[[i]]$height <- height$height[i]
bcs[[i]]$width <- width$width[i]
}
names(bcs) <- sample_names
head(bcs[[1]])
Read in the matrix, barcodes, and genes
For the most simplistic approach we are going to read in our filtered_feature_bc_matrix.h5 using the Seurat package. However, if you don’t have access to this package you can read in the files from the filtered_feature_bc_matrix directory and reconstruct the data.frame with the barcodes as the row names and the genes as the column names. You can see a code example below
matrix <- list()
for (i in 1:length(sample_names)) {
matrix[[i]] <- as.data.frame(t(Read10X_h5(matrix_paths[i])))
}
head(matrix[[1]])
Read from filtered_feature_bc_matrix directory. You can modify as above to write a loop to read these in.
matrix_dir = "/path/to/Sample1/outs/filtered_feature_bc_matrix/"
barcode.path <- paste0(matrix_dir, "barcodes.tsv.gz")
features.path <- paste0(matrix_dir, "features.tsv.gz")
matrix.path <- paste0(matrix_dir, "matrix.mtx.gz")
matrix <- t(readMM(file = matrix.path))
feature.names = read.delim(features.path,
header = FALSE,
stringsAsFactors = FALSE)
barcode.names = read.delim(barcode.path,
header = FALSE,
stringsAsFactors = FALSE)
rownames(matrix) = barcode.names$V1
colnames(matrix) = feature.names$V2
You can also parallelize this step using the doSNOW library if you are analyzing lots of samples
library(doSNOW)
cl <- makeCluster(4)
registerDoSNOW(cl)
i = 1
matrix<- foreach(i=1:length(sample_names), .packages = c("Matrix", "Seurat")) %dopar% {
as.data.frame(t(Read10X_h5(matrix_paths[i])))
}
stopCluster(cl)
matrix[[1]]
Make summary data.frames
Total UMI per spot
umi_sum <- list()
for (i in 1:length(sample_names)) {
umi_sum[[i]] <- data.frame(barcode = row.names(matrix[[i]]),
sum_umi = Matrix::rowSums(matrix[[i]]))
}
names(umi_sum) <- sample_names
umi_sum <- bind_rows(umi_sum, .id = "sample")
head(umi_sum)
Total Genes per spot
gene_sum <- list()
for (i in 1:length(sample_names)) {
gene_sum[[i]] <- data.frame(barcode = row.names(matrix[[i]]),
sum_gene = Matrix::rowSums(matrix[[i]] != 0))
}
names(gene_sum) <- sample_names
gene_sum <- bind_rows(gene_sum, .id = "sample")
head(gene_sum)
Merge all the necessary data
In this final data.frame we will have information about your spot barcodes, spot tissue category (in/out), scaled spot row and column position, image size, and summary data.
bcs_merge <- bind_rows(bcs, .id = "sample")
bcs_merge <- merge(bcs_merge,umi_sum, by = c("barcode", "sample"))
bcs_merge <- merge(bcs_merge,gene_sum, by = c("barcode", "sample"))
head(bcs_merge)
Plotting
I find that the most convenient way to plot lots of figures together is to make a list of them and utilize the cowplot package to do the arrangement.
Here, we’ll take bcs_merge and filter for each individual sample in sample_names
We’ll also use the image dimensions specific to each sample to make sure our plots have the correct x and y limits. As seen below.
xlim(0,max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(width)))+
Note: Spots are not to scale
Define our color palette for plotting
myPalette <- colorRampPalette(rev(brewer.pal(11, "Spectral")))
Total UMI per Tissue Covered Spot
plots <- list()
for (i in 1:length(sample_names)) {
plots[[i]] <- bcs_merge %>%
filter(sample ==sample_names[i]) %>%
ggplot(aes(x=imagecol,y=imagerow,fill=sum_umi)) +
geom_spatial(data=images_tibble[i,], aes(grob=grob), x=0.5, y=0.5)+
geom_point(shape = 21, colour = "black", size = 1.75, stroke = 0.5)+
coord_cartesian(expand=FALSE)+
scale_fill_gradientn(colours = myPalette(100))+
xlim(0,max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(width)))+
ylim(max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(height)),0)+
xlab("") +
ylab("") +
ggtitle(sample_names[i])+
labs(fill = "Total UMI")+
theme_set(theme_bw(base_size = 10))+
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black"),
axis.text = element_blank(),
axis.ticks = element_blank())
}
plot_grid(plotlist = plots)

Total Genes per Tissue Covered Spot
plots <- list()
for (i in 1:length(sample_names)) {
plots[[i]] <- bcs_merge %>%
filter(sample ==sample_names[i]) %>%
ggplot(aes(x=imagecol,y=imagerow,fill=sum_gene)) +
geom_spatial(data=images_tibble[i,], aes(grob=grob), x=0.5, y=0.5)+
geom_point(shape = 21, colour = "black", size = 1.75, stroke = 0.5)+
coord_cartesian(expand=FALSE)+
scale_fill_gradientn(colours = myPalette(100))+
xlim(0,max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(width)))+
ylim(max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(height)),0)+
xlab("") +
ylab("") +
ggtitle(sample_names[i])+
labs(fill = "Total Genes")+
theme_set(theme_bw(base_size = 10))+
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black"),
axis.text = element_blank(),
axis.ticks = element_blank())
}
plot_grid(plotlist = plots)

Cluster Assignments per Tissue Covered Spot
plots <- list()
for (i in 1:length(sample_names)) {
plots[[i]] <- bcs_merge %>%
filter(sample ==sample_names[i]) %>%
filter(tissue == "1") %>%
ggplot(aes(x=imagecol,y=imagerow,fill=factor(Cluster))) +
geom_spatial(data=images_tibble[i,], aes(grob=grob), x=0.5, y=0.5)+
geom_point(shape = 21, colour = "black", size = 1.75, stroke = 0.5)+
coord_cartesian(expand=FALSE)+
scale_fill_manual(values = c("#b2df8a","#e41a1c","#377eb8","#4daf4a","#ff7f00","gold", "#a65628", "#999999", "black", "grey", "white", "purple"))+
xlim(0,max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(width)))+
ylim(max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(height)),0)+
xlab("") +
ylab("") +
ggtitle(sample_names[i])+
labs(fill = "Cluster")+
guides(fill = guide_legend(override.aes = list(size=3)))+
theme_set(theme_bw(base_size = 10))+
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black"),
axis.text = element_blank(),
axis.ticks = element_blank())
}
plot_grid(plotlist = plots)
Gene of Interest
Here we want to plot a gene of interest so we’ll bind the bcs_merge data.frame with a subset of our matrix that contains our gene of interest. In this case it will be the hippocampus specific gene Hpca. Keep in mind this is an example for mouse, for humans the gene symbol would be HPCA.
plots <- list()
for (i in 1:length(sample_names)) {
plots[[i]] <- bcs_merge %>%
filter(sample ==sample_names[i]) %>%
bind_cols(select(matrix[[i]], "Hpca")) %>%
ggplot(aes(x=imagecol,y=imagerow,fill=Hpca)) +
geom_spatial(data=images_tibble[i,], aes(grob=grob), x=0.5, y=0.5)+
geom_point(shape = 21, colour = "black", size = 1.75, stroke = 0.5)+
coord_cartesian(expand=FALSE)+
scale_fill_gradientn(colours = myPalette(100))+
xlim(0,max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(width)))+
ylim(max(bcs_merge %>%
filter(sample ==sample_names[i]) %>%
select(height)),0)+
xlab("") +
ylab("") +
ggtitle(sample_names[i])+
theme_set(theme_bw(base_size = 10))+
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.background = element_blank(),
axis.line = element_line(colour = "black"),
axis.text = element_blank(),
axis.ticks = element_blank())
}
plot_grid(plotlist = plots)

LS0tCnRpdGxlOiAiVmlzaXVtIExpZWJlciBFeGFtcGxlIEFuYWx5c2lzIE5vdGVib29rIgphdXRob3I6ICdbU3RlcGhlbiBXaWxsaWFtcywgUGhELl0obWFpbHRvOnN0ZXBoZW4ud2lsbGlhbXNAMTB4Z2Vub21pY3MuY29tKSAxMHggR2Vub21pY3MKICBTZW5pb3IgU2NpZW50aXN0IC0gQ29tcHV0YXRpb25hbCBCaW9sb2d5JwpkYXRlOiAnQ29tcGlsZWQ6IGByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJUIgJWQsICVZIilgJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogbm9uZQogICAgdGhlbWU6IGpvdXJuYWwKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzMnCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxNXB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDE1cHg7Cn0KcHJlIHsKICBmb250LXNpemU6IDE1cHgKfQo8L3N0eWxlPgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjYWNoZSA9IEZBTFNFLAogIGNhY2hlLmxhenkgPSBGQUxTRSwKICB0aWR5ID0gVFJVRQopCmBgYAoKIyBJbnRyb2R1Y3Rpb24KClRoZSBtb3RpdmF0aW9uIGZvciB0aGlzIG5vdGVib29rIGlzIHRvIGFsbG93IGVhcmx5IGFjY2VzcyBzaXRlcyB0byAKCiAgKyBSZWFkIGluIHRoZWlyIFNwYWNlIFJhbmdlciBvdXRwdXRzCiAgICArIEdlbmVzLCBVTUlzLCBDbHVzdGVycwogICAgKyBEb3duLXNhbXBsZWQgaW1hZ2VzLCBzY2FsZSBmYWN0b3JzLCBhbmQgc3BvdCB0aXNzdWUgcG9zaXRpb25zCiAgKyBQbG90IHRoaXMgaW5mb3JtYXRpb24gdG8gbWFrZSBmaWd1cmVzIG9mIHRoZSBmb2xsb3dpbmcgY29tYmluYXRpb25zCiAgICArIFRpc3N1ZSAtIFRvdGFsIFVNSQogICAgKyBUaXNzdWUgLSBUb3RhbCBHZW5lCiAgICArIFRpc3N1ZSAtIENsdXN0ZXIKICAgICsgVGlzc3VlIC0gR2VuZSBvZiBpbnRlcmVzdAogICAgClRoZSBmb2xsb3dpbmcgUiBjb2RlIGlzIGRlc2lnbmVkIHRvIHByb3ZpZGUgYSBiYXNlbGluZSBmb3IgaG93IHRvIGRvIHRoZXNlIGV4cGxvcmF0b3J5IGFuYWx5c2VzLgoKIyBMaWJyYXJpZXMgYW5kIEZ1bmN0aW9ucwoKIyMgTGlicmFyaWVzCmBgYHtyIExpYnJhcmllcywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KE1hdHJpeCkKbGlicmFyeShSbWlzYykKbGlicmFyeShnZ2ZvcmNlKQpsaWJyYXJ5KHJqc29uKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGdyaWQpCmxpYnJhcnkocmVhZGJpdG1hcCkKbGlicmFyeShTZXVyYXQpCmBgYAoKIyMgRnVuY3Rpb25zCgpUaGUgYGdlb21fc3BhdGlhbGAgZnVuY3Rpb24gaXMgZGVmaW5lZCB0byBtYWtlIHBsb3R0aW5nIHlvdXIgdGlzc3VlIGltYWdlIGluIGdncGxvdCBhIHNpbXBsZSB0YXNrLgpgYGB7cn0KZ2VvbV9zcGF0aWFsIDwtICBmdW5jdGlvbihtYXBwaW5nID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICJpZGVudGl0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgICAgIGluaGVyaXQuYWVzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAuLi4pIHsKICAKICBHZW9tQ3VzdG9tIDwtIGdncHJvdG8oCiAgICAiR2VvbUN1c3RvbSIsCiAgICBHZW9tLAogICAgc2V0dXBfZGF0YSA9IGZ1bmN0aW9uKHNlbGYsIGRhdGEsIHBhcmFtcykgewogICAgICBkYXRhIDwtIGdncHJvdG9fcGFyZW50KEdlb20sIHNlbGYpJHNldHVwX2RhdGEoZGF0YSwgcGFyYW1zKQogICAgICBkYXRhCiAgICB9LAogICAgCiAgICBkcmF3X2dyb3VwID0gZnVuY3Rpb24oZGF0YSwgcGFuZWxfc2NhbGVzLCBjb29yZCkgewogICAgICB2cCA8LSBncmlkOjp2aWV3cG9ydCh4PWRhdGEkeCwgeT1kYXRhJHkpCiAgICAgIGcgPC0gZ3JpZDo6ZWRpdEdyb2IoZGF0YSRncm9iW1sxXV0sIHZwPXZwKQogICAgICBnZ3Bsb3QyOjo6Z2duYW1lKCJnZW9tX3NwYXRpYWwiLCBnKQogICAgfSwKICAgIAogICAgcmVxdWlyZWRfYWVzID0gYygiZ3JvYiIsIngiLCJ5IikKICAgIAogICkKICAKICBsYXllcigKICAgIGdlb20gPSBHZW9tQ3VzdG9tLAogICAgbWFwcGluZyA9IG1hcHBpbmcsCiAgICBkYXRhID0gZGF0YSwKICAgIHN0YXQgPSBzdGF0LAogICAgcG9zaXRpb24gPSBwb3NpdGlvbiwKICAgIHNob3cubGVnZW5kID0gc2hvdy5sZWdlbmQsCiAgICBpbmhlcml0LmFlcyA9IGluaGVyaXQuYWVzLAogICAgcGFyYW1zID0gbGlzdChuYS5ybSA9IG5hLnJtLCAuLi4pCiAgKQp9CgpgYGAKCgojIFJlYWRpbmcgaW4geW91ciBkYXRhCgojIyBEZWZpbmUgeW91ciBzYW1wbGVzCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kc2FtcGxlX25hbWVzIDwtIGMoIlNhbXBsZTEiLCAiU2FtcGxlMiIpCnNhbXBsZV9uYW1lcwpgYGAKCiMjIERlZmluZSB5b3VyIHBhdGhzCgpQYXRocyBzaG91bGQgYmUgaW4gdGhlIHNhbWUgb3JkZXIgYXMgdGhlIGNvcnJlc3BvbmRpbmcgc2FtcGxlIG5hbWVzCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmltYWdlX3BhdGhzIDwtIGMoIi9wYXRoL3RvL1NhbXBsZTEtc3BhdGlhbC90aXNzdWVfbG93cmVzX2ltYWdlLnBuZyIsCiAgICAgICAgICAgICAgICAgIi9wYXRoL3RvL1NhbXBsZTItc3BhdGlhbC90aXNzdWVfbG93cmVzX2ltYWdlLnBuZyIpCgpzY2FsZWZhY3Rvcl9wYXRocyA8LSBjKCIvcGF0aC90by9TYW1wbGUxLXNwYXRpYWwvc2NhbGVmYWN0b3JzX2pzb24uanNvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgIi9wYXRoL3RvL1NhbXBsZTItc3BhdGlhbC9zY2FsZWZhY3RvcnNfanNvbi5qc29uIikKCnRpc3N1ZV9wYXRocyA8LSBjKCIvcGF0aC90by9TYW1wbGUxLXNwYXRpYWwvdGlzc3VlX3Bvc2l0aW9uc19saXN0LnR4dCIsCiAgICAgICAgICAgICAgICAgICIvcGF0aC90by9TYW1wbGUyLXNwYXRpYWwvdGlzc3VlX3Bvc2l0aW9uc19saXN0LnR4dCIpCgpjbHVzdGVyX3BhdGhzIDwtIGMoIi9wYXRoL3RvL1NhbXBsZTEvb3V0cy9hbmFseXNpc19jc3YvY2x1c3RlcmluZy9ncmFwaGNsdXN0L2NsdXN0ZXJzLmNzdiIsCiAgICAgICAgICAgICAgICAgICAiL3BhdGgvdG8vU2FtcGxlMi9vdXRzL2FuYWx5c2lzX2Nzdi9jbHVzdGVyaW5nL2dyYXBoY2x1c3QvY2x1c3RlcnMuY3N2IikKCm1hdHJpeF9wYXRocyA8LSBjKCIvcGF0aC90by9TYW1wbGUxL291dHMvZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXguaDUiLAogICAgICAgICAgICAgICAgICAiL3BhdGgvdG8vU2FtcGxlMi9vdXRzL2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4Lmg1IikKCmBgYAoKCiMjIFJlYWQgaW4gZG93biBzYW1wbGVkIGltYWdlcwoKV2UgYWxzbyBuZWVkIHRvIGRldGVybWluZSB0aGUgaW1hZ2UgaGVpZ2h0IGFuZCB3aWR0aCBmb3IgcHJvcGVyIHBsb3R0aW5nIGluIHRoZSBlbmQKCmBgYHtyfQppbWFnZXNfY2wgPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlX25hbWVzKSkgewogIGltYWdlc19jbFtbaV1dIDwtIHJlYWQuYml0bWFwKGltYWdlX3BhdGhzW2ldKQp9CgpoZWlnaHQgPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlX25hbWVzKSkgewogaGVpZ2h0W1tpXV0gPC0gIGRhdGEuZnJhbWUoaGVpZ2h0ID0gbnJvdyhpbWFnZXNfY2xbW2ldXSkpCn0KCmhlaWdodCA8LSBiaW5kX3Jvd3MoaGVpZ2h0KQoKd2lkdGggPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlX25hbWVzKSkgewogd2lkdGhbW2ldXSA8LSBkYXRhLmZyYW1lKHdpZHRoID0gbmNvbChpbWFnZXNfY2xbW2ldXSkpCn0KCndpZHRoIDwtIGJpbmRfcm93cyh3aWR0aCkKYGBgCgoKIyMjIENvbnZlcnQgdGhlIGltYWdlcyB0byBncm9icwoKVGhpcyBzdGVwIHByb3ZpZGVzIGNvbXBhdGliaWxpdHkgd2l0aCBnZ3Bsb3QyCmBgYHtyfQpncm9icyA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKHNhbXBsZV9uYW1lcykpIHsKICBncm9ic1tbaV1dIDwtIHJhc3Rlckdyb2IoaW1hZ2VzX2NsW1tpXV0sIHdpZHRoPXVuaXQoMSwibnBjIiksIGhlaWdodD11bml0KDEsIm5wYyIpKQp9CgppbWFnZXNfdGliYmxlIDwtIHRpYmJsZShzYW1wbGU9ZmFjdG9yKHNhbXBsZV9uYW1lcyksIGdyb2I9Z3JvYnMpCmltYWdlc190aWJibGUkaGVpZ2h0IDwtIGhlaWdodCRoZWlnaHQKaW1hZ2VzX3RpYmJsZSR3aWR0aCA8LSB3aWR0aCR3aWR0aAppbWFnZXNfdGliYmxlCmBgYAoKYGBge3J9CnNjYWxlcyA8LSBsaXN0KCkKCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGVfbmFtZXMpKSB7CiAgc2NhbGVzW1tpXV0gPC0gcmpzb246OmZyb21KU09OKGZpbGUgPSBzY2FsZWZhY3Rvcl9wYXRoc1tpXSkKfQoKc2NhbGVzW1sxXV0KYGBgCgojIyBSZWFkIGluIENsdXN0ZXJzCgpgYGB7cn0KY2x1c3RlcnMgPC0gbGlzdCgpCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGVfbmFtZXMpKSB7CiAgY2x1c3RlcnNbW2ldXSA8LSByZWFkLmNzdihjbHVzdGVyX3BhdGhzW2ldKQp9CgpoZWFkKGNsdXN0ZXJzW1sxXV0pCmBgYAoKCiMjIENvbWJpbmUgY2x1c3RlcnMgYW5kIHRpc3N1ZSBpbmZvIGZvciBlYXN5IHBsb3R0aW5nCgpBdCB0aGlzIHBvaW50IHdlIGFsc28gbmVlZCB0byBhZGp1c3QgdGhlIHNwb3QgcG9zaXRpb25zIGJ5IHRoZSBzY2FsZSBmYWN0b3IgZm9yIHRoZSBpbWFnZSB0aGF0IHdlIGFyZSB1c2luZy4gSW4gdGhpcyBjYXNlIHdlIGFyZSB1c2luZyB0aGUgbG93cmVzIGltYWdlIHdoaWNoIGhhcyBiZWVuIHJlc2l6ZWQgYnkgU3BhY2UgUmFuZ2VyIHRvIGJlIDYwMCBwaXhlbHMgKGxhcmdlc3QgZGltZW5zaW9uKSBidXQgYWxzbyBrZWVwcyB0aGUgcHJvcGVyIHByb3BvcnRpb25zLiAKCkZvciBleGFtcGxlLCBpZiB5b3VyIGltYWdlIGlzIDEyMDAweDExMDAwIHRoZSBpbWFnZSB3aWxsIGJlIHJlc2l6ZWQgdG8gYmUgNjAweDU1MC4gSWYgeW91ciBpbWFnZSBpcyAxMTAwMHgxMjAwMCB0aGUgaW1hZ2Ugd2lsbCBiZSByZXNpemVkIHRvIGJlIDU1MHg2MDAuCmBgYHtyfQpiY3MgPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlX25hbWVzKSkgewogICBiY3NbW2ldXSA8LSByZWFkLmNzdih0aXNzdWVfcGF0aHNbaV0sY29sLm5hbWVzPWMoImJhcmNvZGUiLCJ0aXNzdWUiLCJyb3ciLCJjb2wiLCJpbWFnZXJvdyIsImltYWdlY29sIiksIGhlYWRlciA9IEZBTFNFKQogICBiY3NbW2ldXSRpbWFnZXJvdyA8LSBiY3NbW2ldXSRpbWFnZXJvdyAqIHNjYWxlc1tbaV1dJHRpc3N1ZV9sb3dyZXNfc2NhbGVmICAgICMgc2NhbGUgdGlzc3VlIGNvb3JkaW5hdGVzIGZvciBsb3dyZXMgaW1hZ2UKICAgYmNzW1tpXV0kaW1hZ2Vjb2wgPC0gYmNzW1tpXV0kaW1hZ2Vjb2wgKiBzY2FsZXNbW2ldXSR0aXNzdWVfbG93cmVzX3NjYWxlZgogICBiY3NbW2ldXSR0aXNzdWUgPC0gYXMuZmFjdG9yKGJjc1tbaV1dJHRpc3N1ZSkKICAgYmNzW1tpXV0gPC0gbWVyZ2UoYmNzW1tpXV0sIGNsdXN0ZXJzW1tpXV0sIGJ5LnggPSAiYmFyY29kZSIsIGJ5LnkgPSAiQmFyY29kZSIsIGFsbCA9IFRSVUUpCiAgIGJjc1tbaV1dJGhlaWdodCA8LSBoZWlnaHQkaGVpZ2h0W2ldCiAgIGJjc1tbaV1dJHdpZHRoIDwtIHdpZHRoJHdpZHRoW2ldCn0KCm5hbWVzKGJjcykgPC0gc2FtcGxlX25hbWVzCgpoZWFkKGJjc1tbMV1dKQpgYGAKCiMjIFJlYWQgaW4gdGhlIG1hdHJpeCwgYmFyY29kZXMsIGFuZCBnZW5lcwoKRm9yIHRoZSBtb3N0IHNpbXBsaXN0aWMgYXBwcm9hY2ggd2UgYXJlIGdvaW5nIHRvIHJlYWQgaW4gb3VyIGBmaWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeC5oNWAgdXNpbmcgdGhlIFNldXJhdCBwYWNrYWdlLiBIb3dldmVyLCBpZiB5b3UgZG9uJ3QgaGF2ZSBhY2Nlc3MgdG8gdGhpcyBwYWNrYWdlIHlvdSBjYW4gcmVhZCBpbiB0aGUgZmlsZXMgZnJvbSB0aGUgYGZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4YCBkaXJlY3RvcnkgYW5kIHJlY29uc3RydWN0IHRoZSBkYXRhLmZyYW1lIHdpdGggdGhlIGJhcmNvZGVzIGFzIHRoZSByb3cgbmFtZXMgYW5kIHRoZSBnZW5lcyBhcyB0aGUgY29sdW1uIG5hbWVzLiBZb3UgY2FuIHNlZSBhIGNvZGUgZXhhbXBsZSBiZWxvdyAKCmBgYHtyfQptYXRyaXggPC0gbGlzdCgpCgpmb3IgKGkgaW4gMTpsZW5ndGgoc2FtcGxlX25hbWVzKSkgewogbWF0cml4W1tpXV0gPC0gYXMuZGF0YS5mcmFtZSh0KFJlYWQxMFhfaDUobWF0cml4X3BhdGhzW2ldKSkpCn0KCmhlYWQobWF0cml4W1sxXV0pCmBgYAoKClJlYWQgZnJvbSBgZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXhgIGRpcmVjdG9yeS4gWW91IGNhbiBtb2RpZnkgYXMgYWJvdmUgdG8gd3JpdGUgYSBsb29wIHRvIHJlYWQgdGhlc2UgaW4uIApgYGB7cn0KbWF0cml4X2RpciA9ICIvcGF0aC90by9TYW1wbGUxL291dHMvZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgvIgpiYXJjb2RlLnBhdGggPC0gcGFzdGUwKG1hdHJpeF9kaXIsICJiYXJjb2Rlcy50c3YuZ3oiKQpmZWF0dXJlcy5wYXRoIDwtIHBhc3RlMChtYXRyaXhfZGlyLCAiZmVhdHVyZXMudHN2Lmd6IikKbWF0cml4LnBhdGggPC0gcGFzdGUwKG1hdHJpeF9kaXIsICJtYXRyaXgubXR4Lmd6IikKbWF0cml4IDwtIHQocmVhZE1NKGZpbGUgPSBtYXRyaXgucGF0aCkpCmZlYXR1cmUubmFtZXMgPSByZWFkLmRlbGltKGZlYXR1cmVzLnBhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpiYXJjb2RlLm5hbWVzID0gcmVhZC5kZWxpbShiYXJjb2RlLnBhdGgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpyb3duYW1lcyhtYXRyaXgpID0gYmFyY29kZS5uYW1lcyRWMQpjb2xuYW1lcyhtYXRyaXgpID0gZmVhdHVyZS5uYW1lcyRWMgpgYGAKCgpZb3UgY2FuIGFsc28gcGFyYWxsZWxpemUgdGhpcyBzdGVwIHVzaW5nIHRoZSBkb1NOT1cgbGlicmFyeSBpZiB5b3UgYXJlIGFuYWx5emluZyBsb3RzIG9mIHNhbXBsZXMgCmBgYApsaWJyYXJ5KGRvU05PVykKCmNsIDwtIG1ha2VDbHVzdGVyKDQpCnJlZ2lzdGVyRG9TTk9XKGNsKQoKaSA9IDEKbWF0cml4PC0gZm9yZWFjaChpPTE6bGVuZ3RoKHNhbXBsZV9uYW1lcyksIC5wYWNrYWdlcyA9IGMoIk1hdHJpeCIsICJTZXVyYXQiKSkgJWRvcGFyJSB7CiBhcy5kYXRhLmZyYW1lKHQoUmVhZDEwWF9oNShtYXRyaXhfcGF0aHNbaV0pKSkKfQoKc3RvcENsdXN0ZXIoY2wpCgptYXRyaXhbWzFdXQpgYGAKCgojIyBNYWtlIHN1bW1hcnkgZGF0YS5mcmFtZXMKCgoqKlRvdGFsIFVNSSBwZXIgc3BvdCoqCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnVtaV9zdW0gPC0gbGlzdCgpIAoKZm9yIChpIGluIDE6bGVuZ3RoKHNhbXBsZV9uYW1lcykpIHsKICB1bWlfc3VtW1tpXV0gPC0gZGF0YS5mcmFtZShiYXJjb2RlID0gIHJvdy5uYW1lcyhtYXRyaXhbW2ldXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtX3VtaSA9IE1hdHJpeDo6cm93U3VtcyhtYXRyaXhbW2ldXSkpCiAgCn0KbmFtZXModW1pX3N1bSkgPC0gc2FtcGxlX25hbWVzCgp1bWlfc3VtIDwtIGJpbmRfcm93cyh1bWlfc3VtLCAuaWQgPSAic2FtcGxlIikKaGVhZCh1bWlfc3VtKQpgYGAKCgoqKlRvdGFsIEdlbmVzIHBlciBzcG90KioKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZ2VuZV9zdW0gPC0gbGlzdCgpIAoKZm9yIChpIGluIDE6bGVuZ3RoKHNhbXBsZV9uYW1lcykpIHsKICBnZW5lX3N1bVtbaV1dIDwtIGRhdGEuZnJhbWUoYmFyY29kZSA9ICByb3cubmFtZXMobWF0cml4W1tpXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bV9nZW5lID0gTWF0cml4Ojpyb3dTdW1zKG1hdHJpeFtbaV1dICE9IDApKQogIAp9Cm5hbWVzKGdlbmVfc3VtKSA8LSBzYW1wbGVfbmFtZXMKCmdlbmVfc3VtIDwtIGJpbmRfcm93cyhnZW5lX3N1bSwgLmlkID0gInNhbXBsZSIpCmhlYWQoZ2VuZV9zdW0pCmBgYAoKIyMgTWVyZ2UgYWxsIHRoZSBuZWNlc3NhcnkgZGF0YQoKSW4gdGhpcyBmaW5hbCBkYXRhLmZyYW1lIHdlIHdpbGwgaGF2ZSBpbmZvcm1hdGlvbiBhYm91dCB5b3VyIHNwb3QgYmFyY29kZXMsIHNwb3QgdGlzc3VlIGNhdGVnb3J5IChpbi9vdXQpLCBzY2FsZWQgc3BvdCByb3cgYW5kIGNvbHVtbiBwb3NpdGlvbiwgaW1hZ2Ugc2l6ZSwgYW5kIHN1bW1hcnkgZGF0YS4KCmBgYHtyfQpiY3NfbWVyZ2UgPC0gYmluZF9yb3dzKGJjcywgLmlkID0gInNhbXBsZSIpCmJjc19tZXJnZSA8LSBtZXJnZShiY3NfbWVyZ2UsdW1pX3N1bSwgYnkgPSBjKCJiYXJjb2RlIiwgInNhbXBsZSIpKQpiY3NfbWVyZ2UgPC0gbWVyZ2UoYmNzX21lcmdlLGdlbmVfc3VtLCBieSA9IGMoImJhcmNvZGUiLCAic2FtcGxlIikpCmhlYWQoYmNzX21lcmdlKQpgYGAKCgojIFBsb3R0aW5nCgpJIGZpbmQgdGhhdCB0aGUgbW9zdCBjb252ZW5pZW50IHdheSB0byBwbG90IGxvdHMgb2YgZmlndXJlcyB0b2dldGhlciBpcyB0byBtYWtlIGEgbGlzdCBvZiB0aGVtIGFuZCB1dGlsaXplIHRoZSBgY293cGxvdGAgcGFja2FnZSB0byBkbyB0aGUgYXJyYW5nZW1lbnQuIAoKSGVyZSwgd2UnbGwgdGFrZSBgYmNzX21lcmdlYCBhbmQgZmlsdGVyIGZvciBlYWNoIGluZGl2aWR1YWwgc2FtcGxlIGluIGBzYW1wbGVfbmFtZXNgCgpXZSdsbCBhbHNvIHVzZSB0aGUgaW1hZ2UgZGltZW5zaW9ucyBzcGVjaWZpYyB0byBlYWNoIHNhbXBsZSB0byBtYWtlIHN1cmUgb3VyIHBsb3RzIGhhdmUgdGhlIGNvcnJlY3QgeCBhbmQgeSBsaW1pdHMuIEFzIHNlZW4gYmVsb3cuIAoKYGBgCnhsaW0oMCxtYXgoYmNzX21lcmdlICU+JSAKICAgICAgICAgIGZpbHRlcihzYW1wbGUgPT1zYW1wbGVfbmFtZXNbaV0pICU+JSAKICAgICAgICAgIHNlbGVjdCh3aWR0aCkpKSsKYGBgCgoqKl9Ob3RlOiBTcG90cyBhcmUgbm90IHRvIHNjYWxlXyoqCgpEZWZpbmUgb3VyIGNvbG9yIHBhbGV0dGUgZm9yIHBsb3R0aW5nCmBgYHtyfQpteVBhbGV0dGUgPC0gY29sb3JSYW1wUGFsZXR0ZShyZXYoYnJld2VyLnBhbCgxMSwgIlNwZWN0cmFsIikpKQpgYGAKCiMjIFRvdGFsIFVNSSBwZXIgVGlzc3VlIENvdmVyZWQgU3BvdApgYGB7ciwgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSA4fQpwbG90cyA8LSBsaXN0KCkKCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGVfbmFtZXMpKSB7CgpwbG90c1tbaV1dIDwtIGJjc19tZXJnZSAlPiUgCiAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHg9aW1hZ2Vjb2wseT1pbWFnZXJvdyxmaWxsPXN1bV91bWkpKSArCiAgICAgICAgICAgICAgICBnZW9tX3NwYXRpYWwoZGF0YT1pbWFnZXNfdGliYmxlW2ksXSwgYWVzKGdyb2I9Z3JvYiksIHg9MC41LCB5PTAuNSkrCiAgICAgICAgICAgICAgICBnZW9tX3BvaW50KHNoYXBlID0gMjEsIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxLjc1LCBzdHJva2UgPSAwLjUpKwogICAgICAgICAgICAgICAgY29vcmRfY2FydGVzaWFuKGV4cGFuZD1GQUxTRSkrCiAgICAgICAgICAgICAgICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvdXJzID0gbXlQYWxldHRlKDEwMCkpKwogICAgICAgICAgICAgICAgeGxpbSgwLG1heChiY3NfbWVyZ2UgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHdpZHRoKSkpKwogICAgICAgICAgICAgICAgeWxpbShtYXgoYmNzX21lcmdlICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzYW1wbGUgPT1zYW1wbGVfbmFtZXNbaV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChoZWlnaHQpKSwwKSsKICAgICAgICAgICAgICAgIHhsYWIoIiIpICsKICAgICAgICAgICAgICAgIHlsYWIoIiIpICsKICAgICAgICAgICAgICAgIGdndGl0bGUoc2FtcGxlX25hbWVzW2ldKSsKICAgICAgICAgICAgICAgIGxhYnMoZmlsbCA9ICJUb3RhbCBVTUkiKSsKICAgICAgICAgICAgICAgIHRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAxMCkpKwogICAgICAgICAgICAgICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKcGxvdF9ncmlkKHBsb3RsaXN0ID0gcGxvdHMpCmBgYAoKYGBge3IsIG91dC53aWR0aCA9ICIyMDBweCIsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ+L3B1YmxpY19odG1sL09kaW4vQmV0YS9leGFtcGxlX25vdGVib29rL3N1bV91bWkuanBnIikKYGBgCgojIyBUb3RhbCBHZW5lcyBwZXIgVGlzc3VlIENvdmVyZWQgU3BvdApgYGB7ciwgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSA4fQpwbG90cyA8LSBsaXN0KCkKCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGVfbmFtZXMpKSB7CgpwbG90c1tbaV1dIDwtIGJjc19tZXJnZSAlPiUgCiAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHg9aW1hZ2Vjb2wseT1pbWFnZXJvdyxmaWxsPXN1bV9nZW5lKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9zcGF0aWFsKGRhdGE9aW1hZ2VzX3RpYmJsZVtpLF0sIGFlcyhncm9iPWdyb2IpLCB4PTAuNSwgeT0wLjUpKwogICAgICAgICAgICAgICAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS43NSwgc3Ryb2tlID0gMC41KSsKICAgICAgICAgICAgICAgIGNvb3JkX2NhcnRlc2lhbihleHBhbmQ9RkFMU0UpKwogICAgICAgICAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IG15UGFsZXR0ZSgxMDApKSsKICAgICAgICAgICAgICAgIHhsaW0oMCxtYXgoYmNzX21lcmdlICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzYW1wbGUgPT1zYW1wbGVfbmFtZXNbaV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCh3aWR0aCkpKSsKICAgICAgICAgICAgICAgIHlsaW0obWF4KGJjc19tZXJnZSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc2FtcGxlID09c2FtcGxlX25hbWVzW2ldKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoaGVpZ2h0KSksMCkrCiAgICAgICAgICAgICAgICB4bGFiKCIiKSArCiAgICAgICAgICAgICAgICB5bGFiKCIiKSArCiAgICAgICAgICAgICAgICBnZ3RpdGxlKHNhbXBsZV9uYW1lc1tpXSkrCiAgICAgICAgICAgICAgICBsYWJzKGZpbGwgPSAiVG90YWwgR2VuZXMiKSsKICAgICAgICAgICAgICAgIHRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAxMCkpKwogICAgICAgICAgICAgICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKcGxvdF9ncmlkKHBsb3RsaXN0ID0gcGxvdHMpCmBgYAoKYGBge3IsIG91dC53aWR0aCA9ICIyMDBweCIsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ+L3B1YmxpY19odG1sL09kaW4vQmV0YS9leGFtcGxlX25vdGVib29rL3N1bV9nZW5lLmpwZyIpCmBgYAoKIyMgQ2x1c3RlciBBc3NpZ25tZW50cyBwZXIgVGlzc3VlIENvdmVyZWQgU3BvdApgYGB7ciwgZmlnLndpZHRoID0gMTYsIGZpZy5oZWlnaHQgPSA4fQpwbG90cyA8LSBsaXN0KCkKCmZvciAoaSBpbiAxOmxlbmd0aChzYW1wbGVfbmFtZXMpKSB7CgpwbG90c1tbaV1dIDwtIGJjc19tZXJnZSAlPiUgCiAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lCiAgZmlsdGVyKHRpc3N1ZSA9PSAiMSIpICU+JSAKICAgICAgZ2dwbG90KGFlcyh4PWltYWdlY29sLHk9aW1hZ2Vyb3csZmlsbD1mYWN0b3IoQ2x1c3RlcikpKSArCiAgICAgICAgICAgICAgICBnZW9tX3NwYXRpYWwoZGF0YT1pbWFnZXNfdGliYmxlW2ksXSwgYWVzKGdyb2I9Z3JvYiksIHg9MC41LCB5PTAuNSkrCiAgICAgICAgICAgICAgICBnZW9tX3BvaW50KHNoYXBlID0gMjEsIGNvbG91ciA9ICJibGFjayIsIHNpemUgPSAxLjc1LCBzdHJva2UgPSAwLjUpKwogICAgICAgICAgICAgICAgY29vcmRfY2FydGVzaWFuKGV4cGFuZD1GQUxTRSkrCiAgICAgICAgICAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjYjJkZjhhIiwiI2U0MWExYyIsIiMzNzdlYjgiLCIjNGRhZjRhIiwiI2ZmN2YwMCIsImdvbGQiLCAiI2E2NTYyOCIsICIjOTk5OTk5IiwgImJsYWNrIiwgImdyZXkiLCAid2hpdGUiLCAicHVycGxlIikpKwogICAgICAgICAgICAgICAgeGxpbSgwLG1heChiY3NfbWVyZ2UgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHdpZHRoKSkpKwogICAgICAgICAgICAgICAgeWxpbShtYXgoYmNzX21lcmdlICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzYW1wbGUgPT1zYW1wbGVfbmFtZXNbaV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChoZWlnaHQpKSwwKSsKICAgICAgICAgICAgICAgIHhsYWIoIiIpICsKICAgICAgICAgICAgICAgIHlsYWIoIiIpICsKICAgICAgICAgICAgICAgIGdndGl0bGUoc2FtcGxlX25hbWVzW2ldKSsKICAgICAgICAgICAgICAgIGxhYnMoZmlsbCA9ICJDbHVzdGVyIikrCiAgICAgICAgICAgICAgICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MykpKSsKICAgICAgICAgICAgICAgIHRoZW1lX3NldCh0aGVtZV9idyhiYXNlX3NpemUgPSAxMCkpKwogICAgICAgICAgICAgICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoY29sb3VyID0gImJsYWNrIiksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKcGxvdF9ncmlkKHBsb3RsaXN0ID0gcGxvdHMpCmBgYAoKYGBge3IsIG91dC53aWR0aCA9ICIyMDBweCIsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJ+L3B1YmxpY19odG1sL09kaW4vQmV0YS9leGFtcGxlX25vdGVib29rL2NsdXN0ZXIuanBnIikKYGBgCgojIyBHZW5lIG9mIEludGVyZXN0CgpIZXJlIHdlIHdhbnQgdG8gcGxvdCBhIGdlbmUgb2YgaW50ZXJlc3Qgc28gd2UnbGwgYmluZCB0aGUgYGJjc19tZXJnZWAgZGF0YS5mcmFtZSB3aXRoIGEgc3Vic2V0IG9mIG91ciBgbWF0cml4YCB0aGF0IGNvbnRhaW5zIG91ciBnZW5lIG9mIGludGVyZXN0LiBJbiB0aGlzIGNhc2UgaXQgd2lsbCBiZSB0aGUgaGlwcG9jYW1wdXMgc3BlY2lmaWMgZ2VuZSBfSHBjYV8uIEtlZXAgaW4gbWluZCB0aGlzIGlzIGFuIGV4YW1wbGUgZm9yIG1vdXNlLCBmb3IgaHVtYW5zIHRoZSBnZW5lIHN5bWJvbCB3b3VsZCBiZSBfSFBDQV8uCmBgYHtyLCBmaWcud2lkdGggPSAxNiwgZmlnLmhlaWdodCA9IDh9CnBsb3RzIDwtIGxpc3QoKQoKZm9yIChpIGluIDE6bGVuZ3RoKHNhbXBsZV9uYW1lcykpIHsKCnBsb3RzW1tpXV0gPC0gYmNzX21lcmdlICU+JSAKICAgICAgICAgICAgICAgICAgZmlsdGVyKHNhbXBsZSA9PXNhbXBsZV9uYW1lc1tpXSkgJT4lIAogICAgICAgICAgICAgICAgICBiaW5kX2NvbHMoc2VsZWN0KG1hdHJpeFtbaV1dLCAiSHBjYSIpKSAlPiUgCiAgZ2dwbG90KGFlcyh4PWltYWdlY29sLHk9aW1hZ2Vyb3csZmlsbD1IcGNhKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9zcGF0aWFsKGRhdGE9aW1hZ2VzX3RpYmJsZVtpLF0sIGFlcyhncm9iPWdyb2IpLCB4PTAuNSwgeT0wLjUpKwogICAgICAgICAgICAgICAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS43NSwgc3Ryb2tlID0gMC41KSsKICAgICAgICAgICAgICAgIGNvb3JkX2NhcnRlc2lhbihleHBhbmQ9RkFMU0UpKwogICAgICAgICAgICAgICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3VycyA9IG15UGFsZXR0ZSgxMDApKSsKICAgICAgICAgICAgICAgIHhsaW0oMCxtYXgoYmNzX21lcmdlICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihzYW1wbGUgPT1zYW1wbGVfbmFtZXNbaV0pICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCh3aWR0aCkpKSsKICAgICAgICAgICAgICAgIHlsaW0obWF4KGJjc19tZXJnZSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoc2FtcGxlID09c2FtcGxlX25hbWVzW2ldKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoaGVpZ2h0KSksMCkrCiAgICAgICAgICAgICAgICB4bGFiKCIiKSArCiAgICAgICAgICAgICAgICB5bGFiKCIiKSArCiAgICAgICAgICAgICAgICBnZ3RpdGxlKHNhbXBsZV9uYW1lc1tpXSkrCiAgICAgICAgICAgICAgICB0aGVtZV9zZXQodGhlbWVfYncoYmFzZV9zaXplID0gMTApKSsKICAgICAgICAgICAgICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKGNvbG91ciA9ICJibGFjayIpLAogICAgICAgICAgICAgICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpCn0KCnBsb3RfZ3JpZChwbG90bGlzdCA9IHBsb3RzKQpgYGAKCmBgYHtyLCBvdXQud2lkdGggPSAiMjAwcHgiLCBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygifi9wdWJsaWNfaHRtbC9PZGluL0JldGEvZXhhbXBsZV9ub3RlYm9vay9ocGNhLmpwZyIpCmBgYA==